package com.fei.jvmtest;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.LocalVariableNode;
import org.objectweb.asm.tree.MethodNode;

public class MethodParamNamesScanner {

	/**
	 * 获取方法参数名列表
	 * 
	 * @param clazz
	 * @param m
	 * @return
	 * @throws IOException
	 */
	public static List<String> getMethodParamNames(Class<?> clazz, Method m) throws IOException {
		try (InputStream in = clazz.getResourceAsStream("/" + clazz.getName().replace('.', '/') + ".class")) {
			return getMethodParamNames(in, m);
		}

	}

	public static List<String> getMethodParamNames(InputStream in, Method m) throws IOException {
		try (InputStream ins = in) {
			return getParamNames(ins,
					new EnclosingMetadata(m.getName(), Type.getMethodDescriptor(m), m.getParameterTypes().length));
		}

	}

	/**
	 * 获取构造器参数名列表
	 * 
	 * @param clazz
	 * @param constructor
	 * @return
	 */
	public static List<String> getConstructorParamNames(Class<?> clazz, Constructor<?> constructor) {
		try (InputStream in = clazz.getResourceAsStream("/" + clazz.getName().replace('.', '/') + ".class")) {
			return getConstructorParamNames(in, constructor);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return new ArrayList<String>();
	}

	public static List<String> getConstructorParamNames(InputStream ins, Constructor<?> constructor) {
		try (InputStream in = ins) {
			return getParamNames(in, new EnclosingMetadata(constructor.getName(),
					Type.getConstructorDescriptor(constructor), constructor.getParameterTypes().length));
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		return new ArrayList<String>();
	}

	/**
	 * 获取参数名列表辅助方法
	 * 
	 * @param in
	 * @param m
	 * @return
	 * @throws IOException
	 */
	private static List<String> getParamNames(InputStream in, EnclosingMetadata m) throws IOException {
		ClassReader cr = new ClassReader(in);
		ClassNode cn = new ClassNode();
		cr.accept(cn, ClassReader.EXPAND_FRAMES);// 建议EXPAND_FRAMES
		// ASM树接口形式访问
		List<MethodNode> methods = cn.methods;
		List<String> list = new ArrayList<String>();
		for (int i = 0; i < methods.size(); ++i) {
			List<LocalVariable> varNames = new ArrayList<LocalVariable>();
			MethodNode method = methods.get(i);
			// 验证方法签名
			if (method.desc.equals(m.desc) && method.name.equals(m.name)) {
				// System.out.println("desc->"+method.desc+":"+m.desc);
				List<LocalVariableNode> local_variables = method.localVariables;
				for (int l = 0; l < local_variables.size(); l++) {
					String varName = local_variables.get(l).name;
					// index-记录了正确的方法本地变量索引。(方法本地变量顺序可能会被打乱。而index记录了原始的顺序)
					int index = local_variables.get(l).index;
					if (!"this".equals(varName)) // 非静态方法,第一个参数是this
						varNames.add(new LocalVariable(index, varName));
				}
				LocalVariable[] tmpArr = varNames.toArray(new LocalVariable[varNames.size()]);
				// 根据index来重排序，以确保正确的顺序
				Arrays.sort(tmpArr);
				for (int j = 0; j < m.size; j++) {
					list.add(tmpArr[j].name);
				}
				break;

			}

		}
		return list;
	}

	/**
	 * 方法本地变量索引和参数名封装
	 * 
	 * @author xby Administrator
	 */
	static class LocalVariable implements Comparable<LocalVariable> {
		public int index;
		public String name;

		public LocalVariable(int index, String name) {
			this.index = index;
			this.name = name;
		}

		public int compareTo(LocalVariable o) {
			return this.index - o.index;
		}
	}

	/**
	 * 封装方法描述和参数个数
	 * 
	 * @author xby Administrator
	 */
	static class EnclosingMetadata {
		// method name
		public String name;
		// method description
		public String desc;
		// params size
		public int size;

		public EnclosingMetadata(String name, String desc, int size) {
			this.name = name;
			this.desc = desc;
			this.size = size;
		}
	}

	/*public static void main(String[] args) throws IOException {
		for (Method m : AdminController.class.getDeclaredMethods()) {
			List<String> list = getMethodParamNames(AdminController.class, m);
			System.out.println(m.getName() + ":");
			for (String str : list) {
				System.out.println(str);
			}
			System.out.println("------------------------");
		}
	}*/
	
	public static void main(String[] args) throws Exception{
		
		FileInputStream in = new FileInputStream(new File("D:\\Project\\zy\\maven-test\\target\\classes\\com\\fei\\jvmtest\\FindClassFromJar.class"));
		ClassReader cr = new ClassReader(in);
		ClassNode cn = new ClassNode();
		cr.accept(cn, ClassReader.EXPAND_FRAMES);// 建议EXPAND_FRAMES
		// ASM树接口形式访问
		List<MethodNode> methods = cn.methods;
		List<String> list = new ArrayList<String>();
		for (int i = 0; i < methods.size(); ++i) {
			List<LocalVariable> varNames = new ArrayList<LocalVariable>();
			MethodNode method = methods.get(i);
			// 验证方法签名
			/*if (method.desc.equals(m.desc) && method.name.equals(m.name)) {
				// System.out.println("desc->"+method.desc+":"+m.desc);
				List<LocalVariableNode> local_variables = method.localVariables;
				for (int l = 0; l < local_variables.size(); l++) {
					String varName = local_variables.get(l).name;
					// index-记录了正确的方法本地变量索引。(方法本地变量顺序可能会被打乱。而index记录了原始的顺序)
					int index = local_variables.get(l).index;
					if (!"this".equals(varName)) // 非静态方法,第一个参数是this
						varNames.add(new LocalVariable(index, varName));
				}
				LocalVariable[] tmpArr = varNames.toArray(new LocalVariable[varNames.size()]);
				// 根据index来重排序，以确保正确的顺序
				Arrays.sort(tmpArr);
				for (int j = 0; j < m.size; j++) {
					list.add(tmpArr[j].name);
				}
				break;

			}*/
			System.out.println("#########################");
			System.out.println(method.desc + "\t" + method.name);
			System.out.println(method.signature);
			System.out.println(method.instructions);
			System.out.println("#########################");

		}
	}
}